﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BusinessObjects.Base;

namespace Datalayer {
    public class DBBase {
        public Databases DatabaseName { get; set; }
        private ConnectionString _connectionString;

        private List<IEntity> _objects = new List<IEntity>();

        public ConnectionString ConnectionString {
            get {
                if (_connectionString == null)
                    using (DataProvider dataProvider = new DataProvider(DatabaseName)) {
                        _connectionString = dataProvider.ConnectionString;
                    }

                return _connectionString;
            }
        }

        #region Constructors
        public DBBase() {
            this.DatabaseName = Databases.DEFAULT;
        }

        protected DBBase(Databases DatabaseName) {
            this.DatabaseName = DatabaseName;
        }
        #endregion

        public IEnumerable<T> Get<T>() where T : IEntity, new() {
            using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().SelectProcedureName(), ProviderType.StoredProcedure)) {
                return provider.ExecuteEnumerable<T>();
            }
        }

        protected IEnumerable<T> Get<T>(IEnumerable<T> Objects) where T : IEntity, new() {
            List<T> returnData = new List<T>();

            using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().SelectProcedureName(), ProviderType.StoredProcedure)) {
                foreach (var obj in Objects) {
                    provider.AddParam(obj.PrimaryKeyName<T>(), obj.PrimaryKeyValue<T>());
                    IEnumerable<T> returnedData = provider.ExecuteEnumerable<T>();

                    returnData.AddRange(returnedData);
                }

                return returnData;
            }
        }

        protected IEnumerable<T> Get<T>(int ID) where T : IEntity, new() {
            using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().SelectByIDProcedureName(), ProviderType.StoredProcedure)) {
                provider.AddParam(GetObject<T>().PrimaryKeyName(), ID);
                return provider.ExecuteEnumerable<T>();
            }
        }

        public void Delete<T>(IEnumerable<T> Objects) where T : IEntity, new() {
            if (Objects != null & Objects.Count() >= 1) {
                using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().DeleteProcedureName(), ProviderType.StoredProcedure)) {
                    foreach (var obj in Objects) {
                        provider.AddParam(obj.PrimaryKeyName<T>(), obj.PrimaryKeyValue<T>());
                        provider.Execute();
                    }
                }
            }
        }

        protected void Delete(IEnumerable<IEntity> Objects) {
            try {
                GetCollections(Objects);

                if (_objects != null & _objects.Count() >= 1) {
                    foreach (var obj in _objects) {
                        string storedProcedure = (obj as IEntity).DeleteProcedureName();
                        if (storedProcedure != null) {
                            using (DataProvider provider = new DataProvider(DatabaseName, storedProcedure, ProviderType.StoredProcedure)) {
                                provider.AddParam((obj as IEntity).PrimaryKeyName(), (obj as IEntity).PrimaryKeyValue());
                                provider.ExecuteObject(obj);
                            }
                        }
                    }
                }
            } catch (Exception) {
                throw;
            } finally {
                this._objects = null;
            }
        }

        public void Insert<T>(IEnumerable<T> Objects) where T : IEntity, new() {
            if (Objects != null & Objects.Count() >= 1) {
                using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().InsertProcedureName(), ProviderType.StoredProcedure)) {
                    foreach (var obj in Objects) {
                        provider.Execute<T>(obj);
                    }
                }
            }
        }

        protected void Insert(IEnumerable<IEntity> Objects) {
            try {
                GetCollections(Objects);

                if (_objects != null & _objects.Count() >= 1) {
                    foreach (var obj in _objects) {
                        string storedProcedure = (obj as IEntity).InsertProcedureName();
                        if (storedProcedure != null) {
                            using (DataProvider provider = new DataProvider(DatabaseName, storedProcedure, ProviderType.StoredProcedure)) {
                                provider.ExecuteObject(obj);
                            }
                        }
                    }
                }
            } catch (Exception) {
                throw;
            } finally {
                this._objects = null;
            }
        }

        public void Update<T>(IEnumerable<T> Objects) where T : IEntity, new() {
            if (Objects != null & Objects.Count() >= 1) {
                using (DataProvider provider = new DataProvider(DatabaseName, GetObject<T>().UpdateProcedureName(), ProviderType.StoredProcedure)) {
                    foreach (var obj in Objects) {
                        provider.Execute<T>(obj);
                    }
                }
            }
        }

        protected void Update(IEnumerable<IEntity> Objects) {
            try {
                GetCollections(Objects);

                if (_objects != null & _objects.Count() >= 1) {
                    foreach (var obj in _objects) {
                        string storedProcedure = (obj as IEntity).UpdateProcedureName();
                        if (storedProcedure != null) {
                            using (DataProvider provider = new DataProvider(DatabaseName, storedProcedure, ProviderType.StoredProcedure)) {
                                provider.ExecuteObject(obj);
                            }
                        }
                    }
                }
            } catch (Exception) {
                throw;
            } finally {
                this._objects = null;
            }
        }

        private static IEntity GetObject<T>() where T : IEntity, new() {
            return new T();
        }

        private void GetCollections(IEnumerable<IEntity> Objects) {
            foreach (var obj in Objects) {
                GetCollection(obj);
            }
        }

        private void GetCollection(IEntity obj) {
            if (obj == null) return;
            var type = obj.GetType();
            if (typeof(IEntity).IsAssignableFrom(type)) {
                _objects.Add(obj);
                foreach (var prop in type.GetProperties()) {
                    var get = prop.GetGetMethod();
                    if (!get.IsStatic && get.GetParameters().Length == 0) {
                        if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType)) {
                            var collection = (IEnumerable)get.Invoke(obj, null);
                            if (collection != null) {
                                foreach (var obj1 in collection) {
                                    if (typeof(IEntity).IsAssignableFrom(obj1.GetType())) {
                                        GetCollection(obj1 as IEntity);
                                    }
                                }
                            }
                        } else if (typeof(IEntity).IsAssignableFrom(prop.PropertyType)) {
                            GetCollection(get.Invoke(obj, null) as IEntity);
                        }
                    }
                }
            }
        }
    }
}
